/*
 * Copyright 2018-2021 guerlab.net and other contributors.
 *
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.guerlab.smart.pay.web.wx;

import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.extern.slf4j.Slf4j;
import net.guerlab.commons.exception.ApplicationException;
import net.guerlab.commons.number.NumberHelper;
import net.guerlab.smart.pay.core.enums.PayStatus;
import net.guerlab.smart.pay.service.entity.PayLog;
import net.guerlab.smart.pay.service.entity.PayLogNotify;
import net.guerlab.smart.pay.service.service.NotifyOriginalLogService;
import net.guerlab.smart.pay.service.service.PayLogNotifyService;
import net.guerlab.smart.pay.service.service.PayLogService;
import net.guerlab.spring.web.annotation.IgnoreResponseHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;

/**
 * 抽象微信支付通知控制器
 *
 * @author guer
 */
@Slf4j
public abstract class AbstractWxPayNotifyController extends AbstractWxPayController {

    private PayLogService logService;

    private PayLogNotifyService notifyService;

    private NotifyOriginalLogService notifyOriginalLogService;

    @Operation(description = "回调通知")
    @PostMapping("/{appId}")
    @IgnoreResponseHandler
    public final String notify(@Parameter(name = "微信应用ID", required = true) @PathVariable String appId, @RequestBody String xmlData) {
        String result = notify0(appId, xmlData);
        notifyOriginalLogService.add(getPayChannel(), xmlData, result);
        return result;
    }

    protected final String notify0(String appId, String xmlData) {
        WxPayOrderNotifyResult notifyResult;
        try {
            notifyResult = buildWxPayService(appId).parseOrderNotifyResult(xmlData);
        } catch (ApplicationException e) {
            return WxPayNotifyResponse.fail(e.getLocalizedMessage());
        } catch (Exception e) {
            return WxPayNotifyResponse.fail("build WxPayService faild");
        }

        long payLogId;
        try {
            payLogId = Long.parseLong(notifyResult.getOutTradeNo());
        } catch (Exception e) {
            return WxPayNotifyResponse.fail(e.getLocalizedMessage());
        }

        BigDecimal amount = new BigDecimal(notifyResult.getTotalFee()).divide(WxPayServiceConstant.HUNDRED, 2, RoundingMode.FLOOR);
        String notifyAppId = notifyResult.getAppid();
        if (!Objects.equals(appId, notifyAppId)) {
            return WxPayNotifyResponse.fail("appId not equals");
        }

        PayLog payLog = logService.selectById(payLogId);

        if (payLog == null) {
            return WxPayNotifyResponse.fail("payLog invalid");
        } else if (!Objects.equals(getPayChannel(), payLog.getPayChannel())) {
            return WxPayNotifyResponse.fail("payLog payChannel error, need: " + payLog.getPayChannel() + ", input: " + getPayChannel());
        } else if (!NumberHelper.isEquals(amount, payLog.getAmount())) {
            return WxPayNotifyResponse.fail("payLog amount error, need: " + payLog.getAmount() + ", input: " + amount);
        } else if (!Objects.equals(appId, payLog.getExtend().get(KEY_APP_ID))) {
            return WxPayNotifyResponse.fail("payLog appId error, need: " + payLog.getExtend() + ", input: " + appId);
        } else if (payLog.getPayStatus() != PayStatus.WAIT_PAY) {
            return WxPayNotifyResponse.fail("payLog status error: " + payLog.getPayStatus());
        }

        PayLogNotify payLogNotify = PayLogNotify.buildByPayLog(payLog);
        payLogNotify.setOutOrderSn(notifyResult.getTransactionId());

        try {
            notifyService.insert(payLogNotify);
        } catch (Exception e) {
            log.debug(e.getLocalizedMessage(), e);
        }

        logService.payed(payLog);

        return WxPayNotifyResponse.success("OK");
    }

    @Autowired
    public void setLogService(PayLogService logService) {
        this.logService = logService;
    }

    @Autowired
    public void setNotifyService(PayLogNotifyService notifyService) {
        this.notifyService = notifyService;
    }

    @Autowired
    public void setNotifyOriginalLogService(NotifyOriginalLogService notifyOriginalLogService) {
        this.notifyOriginalLogService = notifyOriginalLogService;
    }
}
